Skip to content

[API实践]函数调用与工具使用

本节目标

  • 理解函数调用(Function Calling)的概念和工作原理
  • 掌握DeepSeek API函数调用的基本语法和参数设置
  • 学习如何设计和实现自定义函数
  • 通过实例了解函数调用在实际应用中的使用方法
  • 构建具有工具使用能力的智能助手

函数调用概述

什么是函数调用

函数调用(Function Calling)是大语言模型的一项重要能力,它允许模型生成结构化的函数调用,从而能够:

  1. 与外部工具和API交互:如查询天气、执行数据库操作、调用第三方服务等
  2. 执行特定任务:如日期计算、单位转换、数据分析等
  3. 返回结构化数据:确保输出符合预定义的格式,便于后续处理

类比理解:如果将AI比作一个智能助手,那么函数调用就像是给这个助手提供了一系列可使用的工具。助手不仅能听懂你的请求,还能选择合适的工具来完成任务,比如使用计算器进行计算,或使用地图应用查询路线。

函数调用的工作流程

函数调用的基本工作流程包括以下步骤:

  1. 定义函数:开发者预先定义可用的函数及其参数规范
  2. 用户发起请求:用户提出需要解决的问题
  3. 模型选择函数:模型根据用户请求,决定是否调用函数以及调用哪个函数
  4. 生成参数:模型生成符合函数要求的参数
  5. 执行函数:开发者的应用程序接收函数调用请求,执行相应的函数
  6. 处理结果:将函数执行结果返回给模型,模型继续对话

DeepSeek API的函数调用

DeepSeek API支持函数调用功能,使用方法与OpenAI的函数调用类似。下面介绍基本用法:

基本语法

python
from openai import OpenAI

def send_messages(messages):
    response = client.chat.completions.create(
        model="deepseek-chat",
        messages=messages,
        tools=tools
    )
    return response.choices[0].message

client = OpenAI(
    api_key="<your api key>",
    base_url="https://api.deepseek.com",
)

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get weather of an location, the user shoud supply a location first",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The city and state, e.g. San Francisco, CA",
                    }
                },
                "required": ["location"]
            },
        }
    },
]

messages = [{"role": "user", "content": "How's the weather in Hangzhou?"}]
message = send_messages(messages)
print(f"User>\t {messages[0]['content']}")

tool = message.tool_calls[0]
messages.append(message)

messages.append({"role": "tool", "tool_call_id": tool.id, "content": "24℃"})
message = send_messages(messages)
print(f"Model>\t {message.content}")

function_call参数选项

function_call参数控制模型如何使用函数,有以下几种选项:

  • "auto":让模型自行决定是否调用函数
  • {"name": "function_name"}:强制模型调用指定的函数
  • "none":禁止模型调用任何函数,即使提供了函数定义

函数定义的关键要素

每个函数定义包含以下核心要素:

  1. name:函数的唯一标识符,模型将使用它来表示要调用的函数
  2. description:函数的描述,帮助模型理解何时应该使用该函数
  3. parameters:函数接受的参数,使用JSON Schema格式定义
    • type:参数的数据类型(string, number, boolean, object, array等)
    • properties:对象类型参数的属性定义
    • required:必填参数列表
    • description:参数的详细描述

构建实用的函数调用应用

下面,我们将实现一个包含多个函数的助手,展示函数调用的实际应用。

示例:多功能助手应用

python
import json
import datetime
import requests
import math
from openai import OpenAI

# 初始化客户端
client = OpenAI(api_key="sk-0995c6be9ed14b8ca8293cb7811ce681", base_url="https://api.deepseek.com")

# 定义工具列表
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取指定城市的天气信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,如'北京'、'上海'等"
                    },
                    "date": {
                        "type": "string",
                        "description": "查询日期,格式为'YYYY-MM-DD',默认为今天"
                    }
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "calculate_date",
            "description": "计算日期,如计算几天后是什么日期,或两个日期之间相隔多少天",
            "parameters": {
                "type": "object",
                "properties": {
                    "operation": {
                        "type": "string",
                        "enum": ["add", "subtract", "difference"],
                        "description": "操作类型:add(增加天数)、subtract(减少天数)、difference(计算差值)"
                    },
                    "date": {
                        "type": "string",
                        "description": "基准日期,格式为'YYYY-MM-DD',默认为今天"
                    },
                    "days": {
                        "type": "integer",
                        "description": "天数,用于add和subtract操作"
                    },
                    "end_date": {
                        "type": "string",
                        "description": "结束日期,用于difference操作,格式为'YYYY-MM-DD'"
                    }
                },
                "required": ["operation"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "unit_conversion",
            "description": "单位转换,如长度、重量、温度等",
            "parameters": {
                "type": "object",
                "properties": {
                    "value": {
                        "type": "number",
                        "description": "要转换的数值"
                    },
                    "from_unit": {
                        "type": "string",
                        "description": "原始单位,如'km'、'kg'、'celsius'等"
                    },
                    "to_unit": {
                        "type": "string",
                        "description": "目标单位,如'm'、'g'、'fahrenheit'等"
                    }
                },
                "required": ["value", "from_unit", "to_unit"]
            }
        }
    }
]

# 实现各个函数的具体逻辑
def get_weather(city, date=None):
    """获取天气信息(模拟函数)"""
    # 实际应用中应该调用真实的天气API
    weather_data = {
        "北京": {"temperature": "25°C", "condition": "晴", "humidity": "40%"},
        "上海": {"temperature": "28°C", "condition": "多云", "humidity": "65%"},
        "广州": {"temperature": "30°C", "condition": "小雨", "humidity": "80%"}
    }
    
    if city in weather_data:
        result = weather_data[city]
        query_date = date if date else datetime.datetime.now().strftime("%Y-%m-%d")
        return f"{city}{query_date}的天气:温度{result['temperature']}{result['condition']},湿度{result['humidity']}"
    else:
        return f"抱歉,没有找到{city}的天气信息。"

def calculate_date(operation, date=None, days=0, end_date=None):
    """日期计算"""
    if date is None:
        date = datetime.datetime.now().strftime("%Y-%m-%d")
    
    base_date = datetime.datetime.strptime(date, "%Y-%m-%d")
    
    if operation == "add":
        result_date = base_date + datetime.timedelta(days=days)
        return f"{date}{days}天是{result_date.strftime('%Y-%m-%d')}"
    elif operation == "subtract":
        result_date = base_date - datetime.timedelta(days=days)
        return f"{date}{days}天是{result_date.strftime('%Y-%m-%d')}"
    elif operation == "difference":
        if end_date is None:
            return "计算日期差值需要提供结束日期。"
        end = datetime.datetime.strptime(end_date, "%Y-%m-%d")
        diff = abs((end - base_date).days)
        return f"{date}{end_date}相差{diff}天。"
    else:
        return "不支持的操作类型。"

def unit_conversion(value, from_unit, to_unit):
    """单位转换"""
    # 定义转换规则
    conversions = {
        # 长度
        "km_to_m": lambda x: x * 1000,
        "m_to_km": lambda x: x / 1000,
        "m_to_cm": lambda x: x * 100,
        "cm_to_m": lambda x: x / 100,
        "feet_to_m": lambda x: x * 0.3048,
        "m_to_feet": lambda x: x / 0.3048,
        
        # 重量
        "kg_to_g": lambda x: x * 1000,
        "g_to_kg": lambda x: x / 1000,
        "lb_to_kg": lambda x: x * 0.45359237,
        "kg_to_lb": lambda x: x / 0.45359237,
        
        # 温度
        "celsius_to_fahrenheit": lambda x: x * 9/5 + 32,
        "fahrenheit_to_celsius": lambda x: (x - 32) * 5/9
    }
    
    key = f"{from_unit.lower()}_to_{to_unit.lower()}"
    if key in conversions:
        result = conversions[key](value)
        return f"{value} {from_unit} = {result} {to_unit}"
    else:
        return f"不支持从{from_unit}{to_unit}的转换。"

# 函数调用处理器
def process_tool_call(tool_call):
    """处理工具调用请求,执行相应的函数并返回结果"""
    function_name = tool_call.function.name
    arguments = json.loads(tool_call.function.arguments)
    
    if function_name == "get_weather":
        result = get_weather(**arguments)
    elif function_name == "calculate_date":
        result = calculate_date(**arguments)
    elif function_name == "unit_conversion":
        result = unit_conversion(**arguments)
    else:
        result = f"未知的函数: {function_name}"
    
    return result, tool_call.id

# 主要对话循环
def chat_with_tools():
    """带有工具调用功能的对话系统"""
    messages = []
    
    print("欢迎使用多功能助手!您可以查询天气、计算日期或进行单位转换。\n输入'退出'或'exit'结束对话。")
    
    while True:
        user_input = input("\n你: ")
        if user_input.lower() in ["退出", "exit", "quit"]:
            print("感谢使用,再见!")
            break
            
        # 添加用户消息
        messages.append({"role": "user", "content": user_input})
        
        # 调用API
        response = client.chat.completions.create(
            model="deepseek-chat",
            messages=messages,
            tools=tools
        )
        
        # 获取模型的回复
        message = response.choices[0].message
        
        # 处理可能的工具调用
        if message.tool_calls:
            tool_results = []
            
            # 将助手响应添加到消息历史
            messages.append(message)
            
            # 处理所有工具调用
            for tool_call in message.tool_calls:
                # 执行函数并获取结果
                result, tool_call_id = process_tool_call(tool_call)
                
                # 将工具调用结果添加到消息历史
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call_id,
                    "content": result
                })
                
                tool_results.append(result)
            
            # 将工具执行结果提供给模型,让模型生成最终回复
            second_response = client.chat.completions.create(
                model="deepseek-chat",
                messages=messages
            )
            
            assistant_response = second_response.choices[0].message.content
            messages.append({"role": "assistant", "content": assistant_response})
            print(f"\nAI: {assistant_response}")
        else:
            # 没有工具调用,直接显示回复
            messages.append({"role": "assistant", "content": message.content})
            print(f"\nAI: {message.content}")

if __name__ == "__main__":
    chat_with_tools()

运行和测试

将上述代码保存为function_assistant.py,然后运行:

bash
python function_assistant.py

试试以下几种提问:

  1. "北京明天的天气如何?"
  2. "10天后是什么日期?"
  3. "计算2023-01-01和2023-12-31之间相差多少天"
  4. "将25摄氏度转换为华氏度"
  5. "请告诉我5公里等于多少米"

函数设计最佳实践

函数定义原则

  1. 明确的命名:函数名应直观表达其功能
  2. 详细的描述:帮助模型正确理解函数用途
  3. 严格的参数规范:明确定义参数类型、格式和要求
  4. 合理的参数分组:相关参数应分组在同一个函数中
  5. 功能单一:每个函数应专注于单一功能,避免过于复杂

常见函数类型

以下是一些常见的函数调用应用场景:

  1. 信息查询:天气、股票、新闻、维基百科等
  2. 数据处理:计算、排序、过滤、统计等
  3. 时间处理:日期计算、时区转换、提醒设置等
  4. 外部服务集成:电子邮件、日历、笔记、任务管理等
  5. 数据库操作:查询、插入、更新、删除等
  6. 自定义分析:文本分析、情感分析、图像分析等

高级函数调用应用

多函数链式调用

更复杂的场景可能需要依次调用多个函数来完成任务:

python
# 行程计划助手示例
functions = [
    {
        "name": "search_flights",
        "description": "搜索两地之间的航班信息",
        "parameters": {...}
    },
    {
        "name": "search_hotels",
        "description": "搜索目的地的酒店信息",
        "parameters": {...}
    },
    {
        "name": "get_weather_forecast",
        "description": "获取目的地的天气预报",
        "parameters": {...}
    },
    {
        "name": "create_itinerary",
        "description": "根据航班、酒店和天气信息创建行程计划",
        "parameters": {...}
    }
]

在这个例子中,用户可能会提出"帮我规划下周去上海的三天行程",AI会依次调用各个函数,收集必要信息后创建完整行程。

条件函数调用

根据上下文或用户需求动态决定要调用的函数:

python
def determine_functions(user_query):
    """根据用户查询动态确定可用的函数列表"""
    all_functions = [weather_function, date_function, conversion_function, ...]
    
    if "天气" in user_query or "温度" in user_query:
        return [weather_function]
    elif "日期" in user_query or "几天后" in user_query:
        return [date_function]
    elif "转换" in user_query or "等于" in user_query:
        return [conversion_function]
    else:
        # 默认提供所有函数
        return all_functions

处理复杂的函数调用结果

对于复杂的函数调用结果,可能需要特殊处理:

python
def handle_complex_result(function_name, result):
    """处理复杂的函数调用结果"""
    if function_name == "data_analysis":
        # 可能返回图表数据,需要转换为描述性文本
        return convert_chart_to_description(result)
    elif function_name == "database_query":
        # 可能返回大量数据,需要摘要
        return summarize_data(result)
    else:
        # 普通结果直接返回
        return result

错误处理与安全考虑

参数验证

务必在执行函数前验证输入参数的有效性:

python
def validate_and_execute(function_name, arguments):
    """验证参数并执行函数"""
    try:
        # 基本验证
        if function_name == "get_weather":
            if "city" not in arguments:
                return "错误:缺少必要参数'city'"
            
            # 安全检查:确保city是合法值
            allowed_cities = ["北京", "上海", "广州", ...]
            if arguments["city"] not in allowed_cities:
                return f"错误:不支持的城市'{arguments['city']}'"
        
        # 参数验证通过,执行函数
        # ...
    except Exception as e:
        return f"函数执行错误:{str(e)}"

安全限制

确保函数调用不会导致安全问题:

  1. 避免执行系统命令:除非绝对必要,否则不要允许AI执行系统命令
  2. 限制API权限:使用最小权限原则,只授予必要的访问权限
  3. 添加速率限制:防止过度调用外部API
  4. 敏感操作确认:对于敏感操作(如发送邮件、修改数据),添加用户确认步骤

本节小结

  • 函数调用允许大语言模型与外部工具和API交互
  • 函数定义包括名称、描述和参数规范
  • 良好的函数设计应遵循明确命名、详细描述和严格参数规范等原则
  • 在实际应用中,可以实现多函数协作、条件调用等高级功能
  • 安全使用函数调用需要注意参数验证和权限控制

实践任务

  1. 创建一个包含至少三个不同功能函数的聊天助手
  2. 实现一个函数,连接外部API(如天气API、新闻API等)
  3. 尝试设计一个需要多步骤函数调用的复杂任务
  4. 为上述函数添加安全检查和错误处理机制